package com.own.component.util.math.util;

import com.own.component.util.math.exception.MathException;
import lombok.extern.slf4j.Slf4j;

import javax.script.ScriptEngineManager;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * CalculateEngine
 * <p>
 * 计算器计算引擎、计算遇到浮点会有精度丢失
 *
 * @author chenxueli
 * @date 2022-06-15 03:14:15
 */
@Slf4j
public class CalculateEngineUtil {

    private static final char ADD_SYMBOL = '+';
    private static final char SUBTRACT_SYMBOL = '-';
    private static final char MULTIPLY_SYMBOL = '*';
    private static final char DIVIDE_SYMBOL = '/';
    private static final char POWER_SYMBOL = '^';

    private static final String PARENTHESIS_PREFIX = "(";
    private static final String PARENTHESIS_SUFFIX = ")";
    private static final String STRING_ADD_SYMBOL = "+";
    private static final String STRING_SUBTRACT_SYMBOL = "-";
    private static final String STRING_MULTIPLY_SYMBOL = "*";
    private static final String STRING_DIVIDE_SYMBOL = "/";
    private static final String STRING_POWER_SYMBOL = "^";

    /**
     * 浮点计算
     *
     * @param number1  数字1
     * @param number2  数字2
     * @param operator 计算符号
     * @return 计算后的数据
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    private static BigDecimal doubleCalculate(BigDecimal number1, BigDecimal number2, char operator) {
        switch (operator) {
            case ADD_SYMBOL:
                return number1.add(number2);
            case SUBTRACT_SYMBOL:
                return number1.subtract(number2);
            case MULTIPLY_SYMBOL:
                return number1.multiply(number2);
            case DIVIDE_SYMBOL:
                return number1.divide(number2, 15, RoundingMode.HALF_UP);
            case POWER_SYMBOL:
                return BigDecimal.valueOf(Math.pow(number1.doubleValue(), number2.doubleValue()));
            default:
                break;
        }
        throw new MathException("运算符异常！");
    }

    /**
     * 优先级
     *
     * @param c 内容
     * @return 优先级序号
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    private static int priority(String c) {
        if (c == null) {
            return 0;
        }
        // 括号
        else if (c.equals(PARENTHESIS_PREFIX)) {
            return 1;
        }
        // 加减算法
        else if (c.equals(STRING_ADD_SYMBOL) || c.equals(STRING_SUBTRACT_SYMBOL)) {
            return 2;
        }
        // 乘除算法
        else if (c.equals(STRING_MULTIPLY_SYMBOL) || c.equals(STRING_DIVIDE_SYMBOL)) {
            return 3;
        }
        // 幂运算
        else if (c.equals(STRING_POWER_SYMBOL)) {
            return 4;
        }
        throw new MathException("运算符异常！");
    }

    /**
     * 公式变量转换
     *
     * @param formula 表达式
     * @param data    数据信息
     * @return 转换后的内容
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    @SuppressWarnings("all")
    private static <T> String buildExpression(String formula, Map<String, T> data) {
        var regex = "\\$\\{(.+?)\\}";
        var pattern = Pattern.compile(regex);
        var matcher = pattern.matcher(formula);
        var buffer = new StringBuffer();
        while (matcher.find()) {
            var key = matcher.group(1);
            if (!data.containsKey(key)) {
                throw new MathException("计算变量：$\\{{}\\}不存在值", key);
            }
            var value = data.get(key);
            matcher.appendReplacement(buffer, value == null ? "" : value instanceof String ? (String) value : value.toString());
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }

    /**
     * 获取公式中所有的参数信息
     *
     * @param formula 表达式
     * @return 变量列表信息
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    @SuppressWarnings("all")
    private static List<String> listVar(String formula) {
        var varList = new ArrayList<String>();
        var regex = "\\$\\{(.+?)\\}";
        var pattern = Pattern.compile(regex);
        var matcher = pattern.matcher(formula);
        while (matcher.find()) {
            varList.add(matcher.group(1));
        }
        return varList;
    }

    @SuppressWarnings("all")
    public static String calculate(String expr) {
        // 数字栈
        var number = new Stack<BigDecimal>();
        // 符号栈
        var operator = new Stack<String>();
        // 在栈顶压人一个null，配合它的优先级，目的是减少下面程序的判断
        operator.push(null);
        // 将expr打散为运算数和运算符
        var regex = "(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/\\^()]";
        // 这个正则为匹配表达式中的数字或运算符
        var pattern = Pattern.compile(regex);
        var matcher = pattern.matcher(expr);
        while (matcher.find()) {
            var temp = matcher.group();
            // 遇到符号
            if (temp.matches("[+\\-*/\\^()]")) {
                // 遇到左括号，直接入符号栈
                if (temp.equals(PARENTHESIS_PREFIX)) {
                    operator.push(temp);
                }
                // 遇到右括号，"符号栈弹栈取栈顶符号b，数字栈弹栈取栈顶数字a1，数字栈弹栈取栈顶数字a2，计算a2 b a1 ,将结果压入数字栈"，重复引号步骤至取栈顶为左括号，将左括号弹出
                else if (temp.equals(PARENTHESIS_SUFFIX)) {
                    String b;
                    while (!(b = operator.pop()).equals(PARENTHESIS_PREFIX)) {
                        var a1 = number.pop();
                        var a2 = number.pop();
                        number.push(doubleCalculate(a2, a1, b.charAt(0)));
                    }
                }
                // 遇到运算符，满足该运算符的优先级大于栈顶元素的优先级压栈；否则计算后压栈
                else {
                    while (priority(temp) <= priority(operator.peek())) {
                        var a1 = number.pop();
                        var a2 = number.pop();
                        String b = operator.pop();
                        number.push(doubleCalculate(a2, a1, b.charAt(0)));
                    }
                    operator.push(temp);
                }
            }
            // 遇到数字，直接压入数字栈
            else {
                number.push(new BigDecimal(temp));
            }
        }
        // 遍历结束后，符号栈数字栈依次弹栈计算，并将结果压入数字栈
        while (operator.peek() != null) {
            var a1 = number.pop();
            var a2 = number.pop();
            String b = operator.pop();
            number.push(doubleCalculate(a2, a1, b.charAt(0)));
        }
        // 科学计数
        var pop = number.pop();
        var format = NumberFormat.getInstance();
        format.setGroupingUsed(false);
        // 科学计数转换成完全显示
        return format.format(pop);
    }

    /**
     * 计算带变量参数的内容
     *
     * @param formula 计算公式
     * @param data    变量数据
     * @return 计算结果
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    public static String calculate(String formula, Map<String, Number> data) {
        var expression = buildExpression(formula, data);
        return calculate(expression);
    }

    /**
     * 计算公式的布尔值
     *
     * @param formula 布尔值公式
     * @param data    数据
     * @return 是否通过
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    public static <T> boolean booleanCalculate(String formula, Map<String, T> data) {
        var expression = buildExpression(formula, data);
        return booleanCalculate(expression);
    }

    /**
     * 计算公式的布尔值
     *
     * @param expression 布尔值表达式
     * @return 是否通过
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    public static boolean booleanCalculate(String expression) {
        try {
            var manager = new ScriptEngineManager();
            var engine = manager.getEngineByName("nashorn");
            return Boolean.parseBoolean(String.valueOf(engine.eval(expression)));
        } catch (Exception e) {
            log.error("表达式异常");
            return false;
        }
    }

    public static void main(String[] args) throws Exception {
        var map = new HashMap<String, Number>(8);
        map.put("啊", 1);
        map.put("啊1", new BigDecimal(999));
        map.put("a", 10.0);
        System.out.println(CalculateEngineUtil.calculate("(${啊} + ${啊1}) / ${a}", map));
//        System.out.println(CalculateEngineUtil.calculate("${啊}", map));
//        System.out.println(CalculateEngineUtil.listVar("${啊} - ${aa} + ${j加}").toString());
//        System.out.println(CalculateEngineUtil.calculate("${doctor_number}", map));
//        System.out.println(CalculateEngineUtil.booleanCalculate("${value} > 50", map));
//        var map = new HashMap<String, Object>(8);
//        map.put("a", 1);
//        map.put("b", 2);
//        map.put("c", 3);
//        System.out.println("(${a} + ${c})^${b} = " + calculate("(${a} + ${c})^${b}", map));
//        System.out.println("(${a} + ${c})^${b} / 0 = " + calculate("(${a} + ${c})^${b} / 0", map));
//        System.out.println("(${a} + ${c})^(${b} + ${a}) = " + calculate("(${a} + ${c})^(${b} + ${a})", map));
//        System.out.println("${a} + ${c}^(${b} + ${a}) = " + calculate("${a} + ${c}^(${b} + ${a})", map));
//        System.out.println("(2+3)^2 = " + calculate("(2+3)^2"));
//        System.out.println("2+3^2 = " + calculate("2+3^2"));
//        System.out.println("-2.000*(2+3) = " + calculate("-2.000*(2+3)"));
//        System.out.println("-3.5*(4.5-(4+(-1-1/2))) = " + calculate("-3.5*(4.5-(4+(-1-1/2)))"));
//        System.out.println("(1.4-1)*3-5*(2.1-1) = " + calculate("(1.4-1)*3-5*(2.1-1)"));
//        System.out.println("(${a} + ${b} * ${c}) / ${c} = " + calculate("(${a} + ${b} * ${c}) / ${c}", map));
//        System.out.println("${a} + ${b} * ${a} > ${c} => " + booleanCalculate("${a} + ${b} * ${a} > ${c}", map));
//        System.out.println("(${b} * ${a})^2 = " + calculate("(${b} * ${a})^2", map));
//        System.out.println("${a} + (${b} * ${a})^2 > ${c} => " + booleanCalculate("${a} + (${b} * ${a})^2 > ${c}", map));
//        System.out.println("${a} + ${b} * ${a} >= ${c} => " + booleanCalculate("${a} + ${b} * ${a} >= ${c}", map));
//        System.out.println("1 < 2 => " + booleanCalculate("1 < 2", null));
//        System.out.println("1 = 2 => " + booleanCalculate("1 = 2", null));
//        System.out.println("1 = 1 => " + booleanCalculate("1 = 1", null));
//        System.out.println("1 == 1 => " + booleanCalculate("1 == 1", null));
//        System.out.println("1.0 == 1.0 => " + booleanCalculate("1.0 == 1.0", null));
//        System.out.println("1.0 == 1.1 => " + booleanCalculate("1.0 == 1.1", null));
//        System.out.println("'是' == '是' => " + booleanCalculate("'是' == '是'", null));
    }


}
